# -*- coding:utf-8 -*-

# @File     :operator
# @Author   :Yupeng
# @Date     :2018/11/12 19:56

from apps.storage.models.data import DataMetaFieldEnum
from apps.storage.models.template import TemplateFieldEnum
from django.db.models import Q
from enum import Enum
from hte.error.models import HTEError, HTEException
from django.utils.translation import ugettext as _

'''
这基本是高级搜索用到的东西
'''
class MetaOperator(Enum):
    NONE = 'none'
    EQUALS = 'eq'
    NOT_EQUALS = 'ne'
    GREATER_THAN = 'gt'
    GREATER_THAN_OR_EQUALS = 'gte'
    LESS_THAN = 'lt'
    LESS_THAN_OR_EQUALS = 'lte'
    STRING_CONTAINS = 'contains'
    STRING_NOT_CONTAINS = 'ncontains'
    STRING_STARTS_WITH = 'startswith'
    STRING_NOT_STARTS_WITH = 'nstartswith'
    STRING_ENDS_WITH = 'endswith'
    STRING_NOT_ENDS_WITH = 'nendswith'
    IN = 'in'
    NOT_IN = 'nin'

    def __eq__(self, other):
        if isinstance(other, MetaOperator):
            return self.value == other.value
        else:
            return self.value == other

    def can_perform_on(self, meta_field: DataMetaFieldEnum):
        if self == MetaOperator.EQUALS:
            return True
        elif self == MetaOperator.NOT_EQUALS:
            return MetaOperator.EQUALS.can_perform_on(meta_field)
        elif self == MetaOperator.GREATER_THAN:
            return meta_field in (
                DataMetaFieldEnum.CREATED_AT,
                DataMetaFieldEnum.DOWNLOADS,
                DataMetaFieldEnum.VIEWS,
                DataMetaFieldEnum.SCORE
            )
        elif self == MetaOperator.GREATER_THAN_OR_EQUALS:
            return MetaOperator.GREATER_THAN.can_perform_on(meta_field)
        elif self == MetaOperator.LESS_THAN:
            return MetaOperator.GREATER_THAN.can_perform_on(meta_field)
        elif self == MetaOperator.LESS_THAN_OR_EQUALS:
            return MetaOperator.GREATER_THAN.can_perform_on(meta_field)
        elif self == MetaOperator.STRING_CONTAINS:
            return meta_field in (
                DataMetaFieldEnum.TITLE,
                DataMetaFieldEnum.ABSTRACT,
                DataMetaFieldEnum.DOI,
                DataMetaFieldEnum.AUTHOR,
                DataMetaFieldEnum.SOURCE,
                DataMetaFieldEnum.REFERENCE,
            )
        elif self == MetaOperator.STRING_NOT_CONTAINS:
            return MetaOperator.STRING_CONTAINS.can_perform_on(meta_field)
        elif self == MetaOperator.STRING_STARTS_WITH:
            return MetaOperator.STRING_CONTAINS.can_perform_on(meta_field)
        elif self == MetaOperator.STRING_ENDS_WITH:
            return MetaOperator.STRING_CONTAINS.can_perform_on(meta_field)
        elif self == MetaOperator.IN:
            return MetaOperator.EQUALS.can_perform_on(meta_field)
        elif self == MetaOperator.NOT_IN:
            return MetaOperator.EQUALS.can_perform_on(meta_field)

    def _check_value(self, meta_field: DataMetaFieldEnum, value):
        if not isinstance(value, (int, float, str, list)):
            raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                _("Value cannot be of type \"%(type_name)s\"") % {'type_name': type(value).__name__}))

        error_ctx = {'op': self.value, 'field': meta_field.name.lower()}

        if meta_field in (
                DataMetaFieldEnum.TITLE,
                DataMetaFieldEnum.ABSTRACT,
                DataMetaFieldEnum.DOI,
                DataMetaFieldEnum.AUTHOR,
                DataMetaFieldEnum.SOURCE,
                DataMetaFieldEnum.REFERENCE,
                DataMetaFieldEnum.KEYWORDS,
                DataMetaFieldEnum.CATEGORY,
        ):  # 字符串字段
            if self in (MetaOperator.IN, MetaOperator.NOT_IN):
                if not isinstance(value, list):
                    value = [None]  # not legit, always raises exception
                for v in value:
                    if not isinstance(v, str):
                        raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                            _(
                                "Value of operator \"%(op)s\" on field \"%(field)s\" must be a list of string") % error_ctx))
            else:
                if not isinstance(value, str):
                    raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                        _("Value of operator \"%(op)s\" on field \"%(field)s\" must be a string") % error_ctx))

        else:  # 数字或日期字段
            if meta_field == DataMetaFieldEnum.CREATED_AT:
                if not isinstance(value, str):
                    raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                        _("Value of operator \"%(op)s\" on field \"%(field)s\" must be a string") % error_ctx))
            elif meta_field == DataMetaFieldEnum.SCORE:
                if not isinstance(value, (int, float)):
                    raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                        _("Value of operator \"%(op)s\" on field \"%(field)s\" must be a number") % error_ctx))
            else:
                if not isinstance(value, int):
                    raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                        _("Value of operator \"%(op)s\" on field \"%(field)s\" must be a number") % error_ctx))

    def make_q_object(self, meta_field: DataMetaFieldEnum, value):
        self._check_value(meta_field, value)
        if meta_field == DataMetaFieldEnum.KEYWORDS:
            if not isinstance(value, list):
                value = [value]
        if meta_field == DataMetaFieldEnum.CREATED_AT:
            op = '__date'
        else:
            op = ''
        if self == MetaOperator.GREATER_THAN_OR_EQUALS:
            op += '__gte'
        elif self == MetaOperator.GREATER_THAN:
            op += '__gt'
        elif self == MetaOperator.LESS_THAN_OR_EQUALS:
            op += '__lte'
        elif self == MetaOperator.LESS_THAN:
            op += '__lt'
        elif self == MetaOperator.STRING_CONTAINS:
            op += '__contains'
        elif self == MetaOperator.IN:
            op += '__in'
        elif self == MetaOperator.STRING_STARTS_WITH:
            op += '__startswith'
        elif self == MetaOperator.STRING_ENDS_WITH:
            op += '__endswith'
        elif self == MetaOperator.EQUALS:
            if meta_field == DataMetaFieldEnum.KEYWORDS:
                op += '__overlap'

        q = Q(**{meta_field.sql_name + op: value})
        if self in (
                MetaOperator.NOT_EQUALS, MetaOperator.STRING_NOT_CONTAINS, MetaOperator.NOT_IN,
                MetaOperator.STRING_NOT_ENDS_WITH, MetaOperator.STRING_NOT_STARTS_WITH
        ):
            q = ~q
        return q

class DataOperator(Enum):
    ANY = 'any'
    EQUALS = 'eq'
    NOT_EQUALS = 'ne'
    GREATER_THAN = 'gt'
    GREATER_THAN_OR_EQUALS = 'gte'
    LESS_THAN = 'lt'
    LESS_THAN_OR_EQUALS = 'lte'
    STRING_CONTAINS = 'contains'
    STRING_NOT_CONTAINS = 'ncontains'
    STRING_STARTS_WITH = 'startswith'
    STRING_NOT_STARTS_WITH = 'nstartswith'
    STRING_ENDS_WITH = 'endswith'
    STRING_NOT_ENDS_WITH = 'nendswith'
    IN = 'in'
    NOT_IN = 'nin'
    NONE = 'none'
    QUANTIFIER_ALL = 'all'
    QUANTIFIER_EXISTS = 'exists'
    QUANTIFIER_NOT_EXISTS = 'nexists'
    QUANTIFIER_INDEX = 'index'

    def negative(self):
        if self == DataOperator.EQUALS:
            return DataOperator.NOT_EQUALS

        elif self == DataOperator.NOT_EQUALS:
            return DataOperator.EQUALS

        elif self == DataOperator.GREATER_THAN:
            return DataOperator.LESS_THAN_OR_EQUALS

        elif self == DataOperator.GREATER_THAN_OR_EQUALS:
            return DataOperator.LESS_THAN

        elif self == DataOperator.LESS_THAN:
            return DataOperator.GREATER_THAN_OR_EQUALS

        elif self == DataOperator.LESS_THAN_OR_EQUALS:
            return DataOperator.GREATER_THAN

        elif self == DataOperator.STRING_CONTAINS:
            return DataOperator.STRING_NOT_CONTAINS

        elif self == DataOperator.STRING_NOT_CONTAINS:
            return DataOperator.STRING_CONTAINS

        elif self == DataOperator.STRING_STARTS_WITH:
            return DataOperator.STRING_NOT_STARTS_WITH

        elif self == DataOperator.STRING_ENDS_WITH:
            return DataOperator.STRING_NOT_ENDS_WITH

        elif self == DataOperator.IN:
            return DataOperator.NOT_IN

        elif self == DataOperator.NONE:
            return DataOperator.ANY

        elif self == DataOperator.ANY:
            return DataOperator.NONE

        elif self == DataOperator.QUANTIFIER_INDEX:
            return self

        elif self == DataOperator.QUANTIFIER_ALL:
            return DataOperator.QUANTIFIER_EXISTS

        elif self == DataOperator.QUANTIFIER_EXISTS:
            return DataOperator.QUANTIFIER_ALL

        elif self == DataOperator.QUANTIFIER_NOT_EXISTS:
            return DataOperator.QUANTIFIER_EXISTS

    def __invert__(self):
        return self.negative()

    def check_value(self, field_type: TemplateFieldEnum, value):

        self.can_perform_on(field_type)
        error_ctx = {'op': self.value, 'field_type': field_type.description}
        if field_type not in (
            TemplateFieldEnum.GENERATOR,
            TemplateFieldEnum.CONTAINER,
            TemplateFieldEnum.TABLE,
            TemplateFieldEnum.ARRAY
        ):

            if not isinstance(value, (int, float, str, list)):
                raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                    _("Value cannot be of type \"%(type_name)s\"") % {'type_name': type(value).__name__}))

            if field_type in (
                TemplateFieldEnum.STRING,
                TemplateFieldEnum.CHOICE
            ):  # 字符串
                if self in (DataOperator.IN, DataOperator.NOT_IN):
                    if not isinstance(value, list):
                        value = [None]  # not legit, always raises exception
                    for v in value:
                        if not isinstance(v, str):
                            raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                                _(
                                    "Value of operator \"%(op)s\" on field \"%(field_type)s\" must be a list of string") % error_ctx))
                else:
                    if not isinstance(value, str):
                        raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                            _(
                                "Value of operator \"%(op)s\" on field \"%(field_type)s\" must be a string") % error_ctx))
            else:  # 数字
                if self in (DataOperator.IN, DataOperator.NOT_IN):
                    if not isinstance(value, list):
                        value = [None]
                    for v in value:
                        if not isinstance(v, (int, float)):
                            raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                                _(
                                    "Value of operator \"%(op)s\" on field \"%(field_type)s\" must be of a list of number") % error_ctx))
                else:
                    if not (isinstance(value, (int, float))):
                        raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                            _(
                                "Value of operator \"%(op)s\" on field \"%(field_type)s\" must be a number") % error_ctx))

    def can_perform_on(self, field_type: TemplateFieldEnum):
        error_ctx = {'op': self.value, 'field_type': field_type.description}
        if field_type in (
            TemplateFieldEnum.TABLE,
            TemplateFieldEnum.ARRAY,
        ):
            if self not in (
                DataOperator.ANY,
                DataOperator.NONE,
                DataOperator.QUANTIFIER_EXISTS,
                DataOperator.QUANTIFIER_ALL,
                DataOperator.QUANTIFIER_NOT_EXISTS,
                DataOperator.QUANTIFIER_INDEX
            ):
                raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                    _(
                        "Cannot use operator \"%(op)s\" on field \"%(field_type)s\"") % error_ctx))

        elif field_type in (
            TemplateFieldEnum.GENERATOR,
            TemplateFieldEnum.CONTAINER,

        ):
            if self not in (DataOperator.ANY, DataOperator.NONE):
                raise HTEException(HTEError.BAD_QUERY_SYNTAX(
                    _(
                        "Cannot use operator \"%(op)s\" on field \"%(field_type)s\"") % error_ctx))
        else:
            if self == DataOperator.EQUALS:
                return field_type in (
                    TemplateFieldEnum.STRING,
                    TemplateFieldEnum.NUMBER,
                    TemplateFieldEnum.RANGE,
                    TemplateFieldEnum.CHOICE,
                )
            elif self == DataOperator.NOT_EQUALS:
                return DataOperator.EQUALS.can_perform_on(field_type)
            elif self == DataOperator.GREATER_THAN:
                return field_type in (
                    TemplateFieldEnum.NUMBER,
                    TemplateFieldEnum.RANGE,
                )
            elif self == DataOperator.GREATER_THAN_OR_EQUALS:
                return DataOperator.GREATER_THAN.can_perform_on(field_type)
            elif self == DataOperator.LESS_THAN:
                return DataOperator.GREATER_THAN.can_perform_on(field_type)
            elif self == DataOperator.LESS_THAN_OR_EQUALS:
                return DataOperator.GREATER_THAN.can_perform_on(field_type)
            elif self == DataOperator.STRING_CONTAINS:
                return field_type in (
                    TemplateFieldEnum.STRING,
                    TemplateFieldEnum.CHOICE
                )
            elif self == DataOperator.STRING_NOT_CONTAINS:
                return DataOperator.STRING_CONTAINS.can_perform_on(field_type)
            elif self == DataOperator.STRING_STARTS_WITH:
                return DataOperator.STRING_CONTAINS.can_perform_on(field_type)
            elif self == DataOperator.STRING_ENDS_WITH:
                return DataOperator.STRING_CONTAINS.can_perform_on(field_type)
            elif self == DataOperator.IN:
                return DataOperator.EQUALS.can_perform_on(field_type)
            elif self == DataOperator.NOT_IN:
                return DataOperator.EQUALS.can_perform_on(field_type)

    @property
    def is_quantifier(self):
        return self in (
            DataOperator.QUANTIFIER_NOT_EXISTS,
            DataOperator.QUANTIFIER_ALL,
            DataOperator.QUANTIFIER_EXISTS,
            DataOperator.QUANTIFIER_INDEX
        )
